fix(subagents): no live subagents can exist at startup - #147
Merged
Conversation
Bootstrap treated any subagent file written in the last 60 s as a running agent and emitted a synthetic subagent-spawned. An agent that finished shortly before a restart therefore reappeared as active — purple activity glyph on the parent, green dot on the group header — until the 30 s stability window declared it complete. The age filter has no basis at bootstrap: Switchboard owns the PTYs its subagents run in, so they all die with the process. Record every pre-existing file as finished, with no IPC. The verdict stays reversible where it could be wrong. Files with a fresh mtime — the only ones that could conceivably still be running — keep a recheck window and are announced late if they grow, so an orphaned writer surviving a hard kill surfaces instead of staying invisible for the whole session. Plainly historical files are frozen outright, which keeps startup free of a per-file statSync. The post-bootstrap path is unchanged.
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
Observed on a restart tonight: a subagent that had already finished showed up as running — violet glyph on the parent session, green dot on the subagent group header — and stayed that way for about thirty seconds.
At startup the scanner treated any subagent transcript whose mtime was under a minute old as a live agent, and announced it. The user had simply restarted shortly after an agent finished.
The fix
The argument that justifies the age filter turns around at startup: nothing on disk can be a live agent when the app boots. Switchboard owns the PTYs — one set per Electron process — and they die with it, taking their agents along. A restored session gets a fresh PTY whose agents write after boot and are detected normally, and a
claudestarted outside Switchboard never enters this path at all.So bootstrap now announces nothing, whatever the mtimes.
But the verdict stays reversible. A first version froze it permanently, which traded a visible thirty-second ghost for a live agent that would have been invisible for the whole session if the ownership argument were ever wrong. Instead, only files with a recent mtime — none or one or two after a restart, never the thousands of history files — get a recheck window. If such a file starts growing, the rehabilitation path added in #142 releases the withheld spawn. If it stays still, nothing was ever announced.
Silence at startup, without the blind spot.
Tests
Four new bootstrap tests, and the two that mattered were verified against the intermediate commit as well as against main: pinning the history/recent distinction, and the full reversible cycle (silence → growth → exactly one late spawn → normal completion). A third pins that the ghost stays gone when nothing moves: three flushes over thirty-one seconds, zero events.
Two existing tests had their setup rewritten — they built their live entry from the synthetic bootstrap spawn, which no longer exists, so they now bootstrap on an empty directory and create the file afterwards. Their assertions are unchanged; one gained an extra assertion. Reviewed line by line.
task check: 671 tests, 664 pass, 0 fail, 7 pre-existing skips, 0 lint errors.Rationale in
.ai/contexts/subagent-observability.md, including why bootstrap needs no age filter and why the recheck window keeps the ownership argument falsifiable.